package demo.impl.icmp;

import java.net.Inet4Address;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

import org.pcap4j.packet.IcmpV4CommonPacket;
import org.pcap4j.packet.IcmpV4EchoPacket;
import org.pcap4j.packet.IcmpV4EchoReplyPacket;
import org.pcap4j.packet.IpV4Packet;
import org.pcap4j.packet.IpV4Packet.IpV4Header;
import org.pcap4j.packet.Packet;
import org.pcap4j.packet.namednumber.IcmpV4Code;
import org.pcap4j.packet.namednumber.IcmpV4Type;
import org.pcap4j.packet.namednumber.IpNumber;

import demo.api.IPv4Service;
import demo.api.IcmpEchoSession;
import demo.api.IcmpService;
import demo.util.AssertUtils;

/**
 * Echo Session
 * 
 * @author caohj
 * @date 2017年8月5日
 */
public class IcmpEchoSessionImpl implements IcmpEchoSession {

	/** echo会话标识符 */
	private short identifier;

	/** echo packet监听器，接收和分发echo reply packet */
	private IcmpEchoListener echoListener;

	private IcmpService icmpService;

	/** 阻塞队列，用来同步Echo request和reply */
	private BlockingQueue<Packet> replyQueue;

	/** 每一个请求一个标识，相同标识的请求才认为是对request的应答 */
	private AtomicInteger sequenceNumber = new AtomicInteger((int) System.currentTimeMillis());

	public IcmpEchoSessionImpl(short identifier, IcmpEchoListener echoListener, IcmpService icmpService) {
		this.identifier = identifier;
		this.echoListener = echoListener;
		this.icmpService = icmpService;
		this.replyQueue = new ArrayBlockingQueue<Packet>(100);

		echoListener.registerEchoSession(identifier, this);
	}

	@Override
	public void close() {
		echoListener.unregisterEchoSession(identifier);
	}

	@Override
	public short getIdentifier() {
		return identifier;
	}

	@Override
	public void gotPacket(Packet packet) {
		if (packet.contains(IcmpV4EchoReplyPacket.class)) {
			// 队列已满就忽略
			replyQueue.offer(packet);
		}
	}

	@Override
	public EchoReplyInfo sendEcho(Inet4Address destip, long timeoutInSecond) throws TimeoutException {
		AssertUtils.notNull(destip, "参数destip不能为空！");
		AssertUtils.isTrue(timeoutInSecond > 0, "参数错误！Echo请求超时不能小于等于0，实际参数=" + timeoutInSecond);

		IcmpV4EchoPacket.Builder echoBuilder = new IcmpV4EchoPacket.Builder();
		short sequenceNumber = (short) this.sequenceNumber.incrementAndGet();
		echoBuilder.identifier(identifier).sequenceNumber(sequenceNumber);
		IcmpV4CommonPacket.Builder commonBuilder = new IcmpV4CommonPacket.Builder();
		commonBuilder.type(IcmpV4Type.ECHO).code(IcmpV4Code.NO_CODE).payloadBuilder(echoBuilder);
		commonBuilder.correctChecksumAtBuild(true);
		IcmpV4CommonPacket packet = commonBuilder.build();

		if (!icmpService.isBound()) {
			throw new RuntimeException("无法发送EchoPacket，尚未绑定ip层服务！destip=" + destip);
		}
		long start = System.currentTimeMillis();
		IPv4Service ipv4 = icmpService.getBaseProtocal();
		ipv4.sendPacket(destip, IpNumber.ICMPV4, packet.getRawData());

		try {
			while (true) {
				long timeout = timeoutInSecond * 1000 - (System.currentTimeMillis() - start);
				if (timeout <= 0) {
					throw new TimeoutException("Echo请求超时！destip=" + destip + ", timeoutInSecond=" + timeoutInSecond);
				} else {
					// 从阻塞队列获取的Packet也有可能是假的(比如站点伪造、收到攻击)，需要再比较sequenceNumber，所有，需要循环处理
					Packet reply = replyQueue.poll(timeout, TimeUnit.MILLISECONDS);
					if (reply != null
							&& reply.get(IcmpV4EchoReplyPacket.class).getHeader().getSequenceNumber() == sequenceNumber) {
						IpV4Header header = reply.get(IpV4Packet.class).getHeader();
						return new EchoReplyInfoImpl(System.currentTimeMillis() - start, header.getTtlAsInt(), header
								.getSrcAddr().getHostAddress());
					}
				}
			}
		} catch (InterruptedException e) {
			throw new RuntimeException("请求失败！请求被中断！InterruptedException.", e);
		}
	}
}
